/*
 * Copyright 2010 J Patrick Davenport
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package v4view.web;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;

import v4view.core.AttributesNotSupportedException;
import v4view.core.Element;

public abstract class PageElement extends Element {
	private boolean						wasLoaded;
	@SuppressWarnings("rawtypes")
	private List<IPageElementCallback>	onLoadCallbacks;
	@SuppressWarnings("rawtypes")
	private List<IPageElementCallback>	beforeReloadCallbacks;
	private String						tag;
	private String						elementId;
	private HttpServletRequest			request;
	private HttpServletResponse			response;
	private PrintWriter					writer;
	private boolean						prettyPrint;
	private int							tabsIn;
	private IParentElement				parent;

	public PageElement() {

	}

	public PageElement(final String _elementId) {
		this.elementId = _elementId;
	}

	public String getTag() {
		return this.tag;
	}

	public PageElement setParent(final IParentElement _parent) {
		if (this.parent != null && !this.parent.equals(_parent)) {
			this.parent.removePageElement(this);
		}
		this.parent = _parent;
		return this;
	}

	public PageElement getParent() {
		return (PageElement) this.parent;
	}

	/**
	 * 
	 * @return the internal id used within the DOM.
	 */
	public String getElementId() {
		if (this.elementId == null) {
			this.elementId = this.getClass().getSimpleName();
		}
		return this.elementId;
	}

	/**
	 * Internal id of the control within the page's DOM. It is not sent to the browser as an element's id.
	 * @param _elementId
	 */
	public PageElement setElementId(final String _elementId) {
		this.elementId = _elementId;
		return this;
	}

	@SuppressWarnings("rawtypes")
	public void addLoadCallBack(final IPageElementCallback _callback) {
		if (this.onLoadCallbacks == null) {
			this.onLoadCallbacks = new ArrayList<IPageElementCallback>();
		}

		this.onLoadCallbacks.add(_callback);
	}

	/**
	 * A hook into the execution of the object before it renders to HTML. This allows for last minute touchups of the DOM.
	 * @param _callBack
	 */
	@SuppressWarnings("rawtypes")
	public void addBeforeRenderCallBack(final IPageElementCallback _callBack) {
		if (this.beforeReloadCallbacks == null) {
			this.beforeReloadCallbacks = new ArrayList<IPageElementCallback>();
		}

		this.beforeReloadCallbacks.add(_callBack);
	}

	/**
	 * Checks to see if the element has callbacks.
	 * @return true if it does.
	 */
	public boolean hasLoadCallbacks() {
		return CollectionUtils.isNotEmpty(this.onLoadCallbacks);
	}

	public boolean hasBeforeRenderCallbacks() {
		return CollectionUtils.isNotEmpty(this.beforeReloadCallbacks);
	}

	/**
	 * Number of callbacks associated with this element.
	 * @return the number of callbacks registered with the element.
	 */
	public int getLoadCallbackCount() {
		if (!this.hasLoadCallbacks()) return 0;
		return this.onLoadCallbacks.size();
	}

	public HttpServletRequest getRequest() {
		return this.request;
	}

	public void setRequest(final HttpServletRequest _request) {
		this.request = _request;
	}

	public HttpServletResponse getResponse() {
		return this.response;
	}

	public void setResponse(final HttpServletResponse _response) {
		this.response = _response;
	}

	public boolean isPrettyPrint() {
		return this.prettyPrint;
	}

	public PageElement setPrettyPrint(final boolean _prettyPrint) {
		this.prettyPrint = _prettyPrint;
		return this;
	}

	protected String getContextPath() {
		return this.getRequest().getContextPath();
	}

	/**
	 * This method should be called by the adapter on the controller side. This method automates the 
	 * event life cycle of all the elements with this element.
	 * @param _request
	 * @param _response
	 * @throws IOException if the element or its children are unable to render.
	 */
	public void render(final HttpServletRequest _request, final HttpServletResponse _response) throws IOException {
		this.setRequest(_request);
		this.setResponse(_response);
		this.load();
		this.render();
	}

	public void render(final HttpServletRequest _request, final Writer _writer) throws IOException {
		this.setRequest(_request);
		this.setWriter(_writer);
		this.load();
		this.render();
	}

	@Override
	public void render() throws IOException {
		this.checkState();
		this.callBeforeRenderCallbacks();
	}

	@SuppressWarnings("unchecked")
	private void callBeforeRenderCallbacks() {
		if (this.hasBeforeRenderCallbacks()) {
			for (@SuppressWarnings("rawtypes")
			final IPageElementCallback callback : this.beforeReloadCallbacks) {
				callback.execute(this);
			}
		}
	}

	/**
	 * Makes sure that the request and response were properly set on the object. Then automatically calls the load callbacks.
	 */
	@Override
	public void load() throws IllegalStateException {
		this.checkState();
		this.callOnLoadCallbacks();
		this.wasLoaded = true;
	}

	protected boolean wasLoaded() {
		return this.wasLoaded;
	}

	protected PageElement setTag(final String _tag) {
		this.tag = _tag;
		return this;
	}

	protected PageElement setTabsIn(final int _tabsIn) {
		this.tabsIn = _tabsIn;
		return this;
	}

	protected int getTabsIn() {
		return this.tabsIn;
	}

	private void checkState() {
		if (this.request == null) { throw new IllegalStateException("The request was not set"); }
		if (this.response == null && this.writer == null) { throw new IllegalStateException("The response and writer not was not set. You must specify one. If you specify both, response is chosen over writer."); }
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void callOnLoadCallbacks() {
		if (this.hasLoadCallbacks()) {
			for (final IPageElementCallback callback : this.onLoadCallbacks) {
				callback.execute(this);
			}
		}
	}

	/**
	 * Adds the value and HTML escapes the entry. If the value is null, it is not added.
	 */
	@Override
	public Element setAttribute(final String _name, final String _value) throws IllegalArgumentException, AttributesNotSupportedException {
		this.setAttribute(_name, _value, true);
		return this;
	}

	public Element setAttribute(final String _name, final String _value, final boolean escapeXML) throws IllegalArgumentException, AttributesNotSupportedException {
		if (StringUtils.isEmpty(_value)) {
			super.removeAttribute(_name);
			return this;
		}

		super.setAttribute(_name, escapeXML ? StringEscapeUtils.escapeHtml(_value == null ? "" : _value) : _value);
		return this;
	}

	public PrintWriter getWriter() throws IOException {
		return this.writer == null ? this.getResponse().getWriter() : this.writer;
	}

	public void setWriter(final Writer _writer) {
		if (_writer == null)
			return;
		if (_writer instanceof PrintWriter) {
			this.writer = (PrintWriter) _writer;
			return;
		}

		this.writer = new PrintWriter(_writer);
	}
}
